
// TODO: need better error failure

var webclip = {}

/**
 * All the constants used by webclip.
 */
webclip.constants = {} 
webclip.constants.FEEDS_ROOT = 'http://docs.google.com/feeds';
webclip.constants.FEEDS_FULL = webclip.constants.FEEDS_ROOT + '/default/private/full';
webclip.constants.DOC_NAME = 'webclip';
webclip.constants.NOTIFICATION_TIMEOUT_MS = 2000;
webclip.constants.NO_ETAGS = '*';

/**
 * Queries list of documents to find a given a document by name.
 *
 * @param {String} documentName The name of the document to query.
 * @param {Function} processQueryResponse Callback to process the response.
 * @param {ChromeExOAuth} requestFactory Factory to create signed requests.
 */
webclip.queryDocumentList = function(documentName, processQueryResponse, requestFactory) {
  var query_params = {
    'method': 'GET',
    'headers': {
      'GData-Version': '3.0',
      'Content-Type': 'application/atom+xml',
    },
    'parameters': {
      'title': documentName,
      'title-exact': 'true'  
    }
  };

  requestFactory.sendSignedRequest(webclip.constants.FEEDS_FULL, processQueryResponse, query_params);
}

/**
 * Finds the docId in the response sent by google docs.
 *
 * @param {String} response  The atom xml response.
 * @returns {String} The docId or null if not found.
 */
webclip.getDocIdFromResponse = function(response) {
  var docIdPrefix = "<gd:resourceId>document:";
  var start = response.indexOf(docIdPrefix);
  var end = response.indexOf("</gd:resourceId>");
  if (start == -1 || end == -1) {
    return null;
  }

  return response.substring(start + docIdPrefix.length, end);
}

/**
 * Finds the etag in the response sent by google docs.
 *
 * @param {String} response  The atom xml response.
 * @returns {String} The etag.
 */
webclip.getEtagsFromResponse = function(response) {
  var etagPrefix = "<entry gd:etag='&quot;";
  var start = response.indexOf(etagPrefix);
  var end = response.indexOf("&quot;'", start + etagPrefix.length);
  if (start == -1 || end == -1) {
     webclip.displayErrorNotification('No etags found');
  }

  return '"' + response.substring(start + etagPrefix.length, end) + '"';
}

/**
 * Creates an empty document.
 *
 * @param {String} documentName The name of the document to create.
 * @param {Function} processCreateResponse Callback to process the response.
 * @param {ChromeExOAuth} requestFactory Factory to create signed requests. 
 */
webclip.createEmptyDocument = function(documentName, processCreateResponse, requestFactory) {
  var create_params = {
    'method': 'POST',
    'headers': {
      'GData-Version': '3.0',
      'Content-Type': 'application/atom+xml',
    },
    body: '<?xml version="1.0" encoding="UTF-8"?>' +
        '<entry xmlns="http://www.w3.org/2005/Atom">' +
        '<category scheme="http://schemas.google.com/g/2005#kind" ' +
        'term="http://schemas.google.com/docs/2007#document"/>' +
        '<title>' + documentName + '</title></entry>'
  };

  requestFactory.sendSignedRequest(webclip.constants.FEEDS_FULL, function(doc) {
    var docId = webclip.getDocIdFromResponse(doc);
    if (docId == null) {
      webclip.displayErrorNotification('Error while saving clip (cannot create document)');

    } else {
      var notification = webkitNotifications.createNotification(
          'clip.png',
          sprintf('Created new document "%s" in google docs.', documentName),
          'All your future webclips will be saved to this document on https://docs.google.com/');
      notification.show();

      processCreateResponse(docId, webclip.constants.NO_ETAGS);
    }
  }, create_params);
}

/**
 * Displays an error message to the user.
 *
 * @param {String} errorMessage Message to display.
 */
webclip.displayErrorNotification = function(errorMessage) {
  var notification = webkitNotifications.createNotification(
    'clip.png', errorMessage, '');
  notification.show();
  throw errorMessage;
}

/**
 * Fetches a document as html.
 *
 * @param {String} docId The id of the document to fetch.
 * @param {Function} callback Callback to process the response.
 * @param {ChromeExOAuth} requestFactory Factory to create signed requests. 
 */
webclip.fetchDocument = function(docId, etag, callback, requestFactory) {
  var get_params = {
    'method': 'GET',
    'headers': {
      'GData-Version': '3.0',
      'Content-Type': 'text/html',
    },
    'parameters': {
      'exportFormat': 'html',
      'format': 'html',
      'id': docId,
    }
  };
  var fetchDocumentUrl = sprintf('%s/download/documents/Export', webclip.constants.FEEDS_ROOT);
  requestFactory.sendSignedRequest(fetchDocumentUrl, callback, get_params);
}

/**
 * Creates the javascript to execute on the page to get the selection. 
 * We pass extra information which we want to store with the selection..
 *
 * @param {String} title The title of the page. 
 * @param {String} url The url of the page. 
 * @returns {String} The javascript code.
 */
webclip.createJsToGetPageSelection = function(title, url) {
  return sprintf(
      'var selection = document.getSelection(); ' +
      'chrome.extension.sendRequest({' +
      '    "selection": selection.toString(),' +
      '    "title": "%s",' +
      '    "url": "%s",' +
      '}, function(response) {});', title, url);
}

webclip.displayNotification = function(title, selection) {
  var notification = webkitNotifications.createNotification('clip.png', title, selection);
  notification.show();

  window.setTimeout(function() {
    notification.cancel();
  }, webclip.constants.NOTIFICATION_TIMEOUT_MS);
}

webclip.createClip = function(title, date, url, selection) {
  var dateAsString = sprintf('%d/%d/%d',
      date.getMonth() + 1, date.getDate(), date.getFullYear());
  return sprintf('<b>%s</b> (%s)<br/><i>%s</i><br/>%s<br/><br/>',
      title, dateAsString, url, selection);
}

webclip.merge = function(previousText, textToAppend) {
  // Remove the last tags to append some text.
  previousText = previousText.substring(
      0, previousText.lastIndexOf('</body>'));

  return sprintf('%s<br/>%s', previousText, textToAppend);
}

/**
 * Updates a document with the given text.
 *
 * @param {String} text New text of the document (as html).
 * @param {String} docId The id of the document to fetch.
 * @param {Function} processResponse Callback to process the response.
 * @param {ChromeExOAuth} requestFactory Factory to create signed requests. 
 */
webclip.updateDocument = function(text, docId, etag, processResponse, requestFactory) {
  var updateUrl = sprintf('%s/default/media/document%%3A%s',
      webclip.constants.FEEDS_ROOT, docId);

  var params = {
    'method': 'PUT',
    'headers': {
      'GData-Version': '3.0',
      'Content-Type': 'text/html',
      'If-Match': etag,
    },
    'body': text 
  };

  requestFactory.sendSignedRequest(updateUrl, processResponse, params);
}

/**
 * Finds a document with the given name (create it if needed).
 *
 * @param {String} name The name of the document to find.
 * @param {Function} callback Callback to execute on the document id found. 
 * @param {ChromeExOAuth} requestFactory Factory to create signed requests. 
 */
webclip.findDocId = function(name, callback, requestFactory) {
  webclip.queryDocumentList(name, function(response) {
    var docId = webclip.getDocIdFromResponse(response);
    if (docId == null) {
      webclip.createEmptyDocument(name, callback, requestFactory);

    } else {
      var etag = webclip.getEtagsFromResponse(response);
      callback(docId, etag);
    }

  }, requestFactory);
}

/**
 * Appends the given text to existing webclips. 
 *
 * @param {String} toSave Clip to save. 
 * @param {Date} date The current date. 
 * @param {ChromeExOAuth} toSaveFactory Factory to create signed toSaves. 
 * @param {Function} notifySaved Callback to notify that the clip has been saved.
 */
webclip.saveSelection = function(documentName, toSave, date, requestFactory,
                                 notifySaved) {

  webclip.findDocId(documentName, function(docId, etag) {

    webclip.fetchDocument(docId, etag, function(previousText) {
      var clip = webclip.createClip(toSave.title, date, toSave.url, toSave.selection);
      var text = webclip.merge(previousText, clip); 

      webclip.updateDocument(text, docId, etag, function(response) {
        notifySaved('clip available on google docs', toSave.selection);
      }, requestFactory);

    }, requestFactory);
  }, requestFactory);
}


webclip.ErrorAwareRequestFactory = function(oauth) {
  this.oauth_ = oauth;

  /**
   * See sendSignedRequest from chrome_ex_oauth.js
   */
  this.sendSignedRequest = function(url, callback, params) {
    this.oauth_.sendSignedRequest(url, function(response, xhr) {
     if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) {
       callback(response);

     } else if (xhr.status == 400) {
       callback(response);

     } else if (xhr.status == 401) {
       webclip.displayErrorNotification('You need to be logged in into your google accounts');

     } else if (xhr.status == 412) {
       webclip.displayErrorNotification('Error while saving clip. ' +
          'Two concurrent updates might have happened at the same time. Try again.');

     } else {
       webclip.displayErrorNotification('Error while saving clip. ');
       //throw "Error returned by XHR : " + xhr.status;
     }
    }, params);
  }
}

/**
 * Initializes webclip.
 * Must be called once only.
 *
 * @param {ChromeExOAuth} oauth Factory to create signed requests. 
 */
webclip.initialize = function(oauth) {
  var requestFactory = new webclip.ErrorAwareRequestFactory(oauth);

  chrome.browserAction.onClicked.addListener(function(tab) {
    var jsToExecute = webclip.createJsToGetPageSelection(tab.title, tab.url);
    chrome.tabs.executeScript(tab.id, {
        'code': jsToExecute,
        'allFrames': false 
    });
  });

  chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
    var text = request.selection;
    if (!text) {
      webclip.displayErrorNotification('Cannot get the selection on this page.');
      // TODO: make it work with iframes, if we set allFrames to true, the script
      // that gets the selection will be executed several times so we need a better
      // way to check if the selection is empty.

      // TODO: we should allow the user to copy/paste the selection

    } else {
     var documentName = localStorage['documentName'] || webclip.constants.DOC_NAME;
     oauth.authorize(function() {
       webclip.saveSelection(documentName, request, new Date(), requestFactory,
                             webclip.displayNotification);
     });
    }
  });
}
